package com.lksoft.bz;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.print.PageFormat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;
import java.util.ResourceBundle;

import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.lksoft.bz.action.AboutAction;
import com.lksoft.bz.action.NewAction;
import com.lksoft.bz.action.OpenAction;
import com.lksoft.bz.action.PageSetupAction;
import com.lksoft.bz.action.PrintAction;
import com.lksoft.bz.action.PrintPreviewAction;
import com.lksoft.bz.action.QuitAction;
import com.lksoft.bz.action.RedoAction;
import com.lksoft.bz.action.SaveAction;
import com.lksoft.bz.action.SaveAsAction;
import com.lksoft.bz.action.SetLetterAction;
import com.lksoft.bz.action.UndoAction;
import com.lksoft.bz.command.ChangeSizeCommand;
import com.lksoft.bz.command.Command;
import com.lksoft.bz.command.History;
import com.lksoft.bz.tool.EraseTool;
import com.lksoft.bz.tool.EyedroperTool;
import com.lksoft.bz.tool.FillTool;
import com.lksoft.bz.tool.LineTool;
import com.lksoft.bz.tool.PencilTool;
import com.lksoft.bz.tool.SelectTool;
import com.lksoft.bz.tool.Tool;

public class Bz implements ActionListener{

	// Bead DB
	protected static final String BEAD_DB_FILE = "/beads_std.txt";

	// Program name
	private static final String PROGRAM_NAME = "B'z";
	
	// GUI resource bundle
	private static final String GUI_RESOURCES = "com.lksoft.bz.GUIResources";
	
	// Frame
	private JFrame frmBz;
	
	// Tools button group
	private ButtonGroup toolButtons;
	
	// Commands history
	History history = new History(100);
	
	// Current file
	File currentFile;
	
	// Current pageFormat
	PageFormat pageFormat = new PageFormat();
	
	// Stitch panel
	StitchPanel stitchPanel = new StitchPanel(this);
	// Palette
	JList<Bead> beadList;
	// Used beads
	JList<BeadUsage> usedBeads;
	// Used beads popup menu
	JPopupMenu usedBeadsPopup;
	// Stitch pattern combo box
	final JComboBox<StitchType> stitchPatternCombo = new JComboBox<>(StitchType.values());
	// Size spinners
	JSpinner widthSpinner;
	JSpinner heightSpinner;
	
	// Tabs
	JTabbedPane eastPane;
	JPanel pattern;
	JPanel palette;
	
	// Menus
	private JMenu fileMenu;
	private JMenu editMenu;
	private JMenu viewMenu;
	private JMenu languageMenu;
	private JMenu imageMenu;
	private JMenu helpMenu;
	
	// Language group
	ButtonGroup languageGroup;
	
	// Languages
	Languages languages = new Languages(this);
	
	// Tools
	Tool[] tools = new Tool[]{
		new PencilTool(this, "/ico_toolpencil.png", "tools.pencil"),
		new EraseTool(this, "/ico_toolerase.png", "tools.erase"),
		new SelectTool(this, "/ico_toolselect.png", "tools.select"),
		new EyedroperTool(this, "/ico_tooleyedroper.png", "tools.eyedroper"),
		new FillTool(this, "/ico_toolfill.png", "tools.fill"),
		new LineTool(this, "/ico_toolline.png", "tools.line"),
	};
	
	// Actions
	NewAction newAction = new NewAction(this, "/ico_new.png");
	OpenAction openAction = new OpenAction(this, "/ico_open.png");
	SaveAction saveAction = new SaveAction(this, "/ico_save.png");
	SaveAsAction saveAsAction = new SaveAsAction(this);
	UndoAction undoAction = new UndoAction(this, "/ico_undo.png");
	RedoAction redoAction = new RedoAction(this, "/ico_redo.png");
	QuitAction quitAction = new QuitAction(this);
	AboutAction aboutAction = new AboutAction(this);
	SetLetterAction setLetterAction = new SetLetterAction(this);
	PrintAction printAction = new PrintAction(this, "/ico_print.png");
	PrintPreviewAction printPreviewAction = new PrintPreviewAction(this);
	PageSetupAction pageSetupAction = new PageSetupAction(this);
	
	// Functions
	Function newFunction = new Function(newAction, "actions.new");
	Function openFunction = new Function(openAction, "actions.open");
	Function saveFunction = new Function(saveAction, "actions.save");
	Function saveAsFunction = new Function(saveAsAction, "actions.saveas");
	Function printFunction = new Function(printAction, "actions.print");
	Function printPreviewFunction = new Function(printPreviewAction, "actions.printpreview");
	Function pageSetupFunction = new Function(pageSetupAction, "actions.pagesetup");
	Function undoFunction = new Function(undoAction, "actions.undo");
	Function redoFunction = new Function(redoAction, "actions.redo");
	Function quitFunction = new Function(quitAction, "actions.quit");
	Function aboutFunction = new Function(aboutAction, "actions.about");
	Function setLetterFunction = new Function(setLetterAction, "actions.setletter");
	
	// Top toolbar
	Function[] topToolbarFunctions = {
			newFunction,
			openFunction,
			saveFunction,
			printFunction,
			undoFunction,
			redoFunction
	};
	
	// All the functions
	Function[] allFunctions = {
			newFunction,
			openFunction,
			saveFunction,
			saveAsFunction,
			printFunction,
			printPreviewFunction,
			pageSetupFunction,
			undoFunction,
			redoFunction,
			quitFunction,
			aboutFunction,
			setLetterFunction
	};

	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					// Read bead db
					try{
						BeadDB.get().openFile(BEAD_DB_FILE);
					} catch(FileNotFoundException e){
						JOptionPane.showMessageDialog(null, Bz.getUIString("messages.errors.nobeaddb") +BEAD_DB_FILE);
						System.exit(1);
					}
					
					// Create window
					Bz window = new Bz();
					window.frmBz.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 * @throws IOException 
	 */
	public Bz() throws IOException {
		initialize();
	}


	@Override
	public void actionPerformed(ActionEvent e) {
		switch( e.getActionCommand() ){
		
		// Stitch pattern change
		case "change_pattern":
			stitchPanel.getStitchPattern().setStitchType((StitchType) stitchPatternCombo.getSelectedItem());
			redraw();
			break;			
		}
	}
	
	/**
	 * Initialize the contents of the frame.
	 * @throws IOException 
	 */
	private void initialize() throws IOException {
		frmBz = new JFrame();
		frmBz.setTitle("B'z");
		frmBz.setBounds(100, 100, 921, 747);
		frmBz.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		frmBz.addWindowListener(quitAction);
		
		eastPane = new JTabbedPane(JTabbedPane.TOP);
		eastPane.setPreferredSize(new Dimension(200, 50));
		eastPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
		frmBz.getContentPane().add(eastPane, BorderLayout.EAST);
		
		// Tabbed pane
		pattern = new JPanel();
		eastPane.addTab(getUIString("tabs.pattern"), null, pattern, null);
		palette = new JPanel();
		eastPane.addTab(getUIString("tabs.palette"), null, palette, null);
		palette.setLayout(new BorderLayout(0, 0));
		pattern.setLayout(new BorderLayout(0, 0));
		
		// Pattern north panel
		JPanel patternNorth = new JPanel();
		patternNorth.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		pattern.add(patternNorth, BorderLayout.NORTH);
		
		// Width\height spinners
		widthSpinner = new JSpinner();
		heightSpinner = new JSpinner();
		
		ChangeListener spinListener = new ChangeListener() {			
			@Override
			public void stateChanged(ChangeEvent e) {
				Command changeSizeCommand = new ChangeSizeCommand(
						Bz.this,
						stitchPanel.getStitchPattern().getWidth(), 
						stitchPanel.getStitchPattern().getHeight(),
						(Integer)widthSpinner.getValue(), 
						(Integer)heightSpinner.getValue());
				// Execute
				changeSizeCommand.redo();
				
				// Register
				addCommand(changeSizeCommand);
			}
		};
		
		widthSpinner.addChangeListener(spinListener);
		heightSpinner.addChangeListener(spinListener);
		
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		patternNorth.add(widthSpinner, c);
		c.gridx = 1;
		patternNorth.add(heightSpinner, c);

		// Stitch pattern combo box
		stitchPatternCombo.setActionCommand("change_pattern");
		stitchPatternCombo.addActionListener(this);
		
		c.gridx = 0;
		c.gridy = 1;
		c.gridwidth = 2;
		patternNorth.add(stitchPatternCombo, c);
		
		// Beads DB
		beadList = new JList<Bead>(new LinkedMapWrapperModel<Integer, Bead>(BeadDB.get().getBeadMap()));
		beadList.setCellRenderer(new BeadRenderer());
		beadList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		JScrollPane beadListScroll = new JScrollPane(beadList);
		palette.add(beadListScroll);
		
		// Used beads popup
		usedBeadsPopup = new JPopupMenu();
		usedBeadsPopup.add(setLetterFunction.getMenuItem());
		
		// Used beads
		usedBeads = new JList<BeadUsage>();
		usedBeads.setCellRenderer(new BeadUsageRenderer());
		usedBeads.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		usedBeads.addListSelectionListener(new ListSelectionListener() {
			
			@Override
			public void valueChanged(ListSelectionEvent e) {
				// Choosing a bead usage automatically sets the palette
				if( usedBeads.getSelectedValue() == null ) return;
				beadList.setSelectedValue(usedBeads.getSelectedValue().getBead(), true);
			}
		});
		usedBeads.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e)  {check(e, true);}
			public void mouseReleased(MouseEvent e) {check(e, false);}
			private void check(MouseEvent e, boolean pressed) {
				if (e.isPopupTrigger()) { //if the event shows the menu
					if( usedBeads.getModel().getSize() == 0 ) return;
					int idx = usedBeads.locationToIndex(e.getPoint());
					if( idx == -1 ) return;
					
					usedBeads.setSelectedIndex(idx); //select the item
					((SetLetterAction) setLetterFunction.getAction()).setBeadUsage(usedBeads.getSelectedValue());
					usedBeadsPopup.show(usedBeads, e.getX(), e.getY()); //and show the menu
			    }
				else{
					if( pressed ) super.mousePressed(e);
					else super.mouseReleased(e);
				}
			}
		});
		
		JScrollPane beadUsageListScroll = new JScrollPane(usedBeads);
		pattern.add(beadUsageListScroll, BorderLayout.CENTER);
		
		JToolBar topToolbar = new JToolBar();
		topToolbar.setFloatable(false);
		frmBz.getContentPane().add(topToolbar, BorderLayout.NORTH);
		
		// Top Toolbar
		for( Function f : this.topToolbarFunctions ){
			topToolbar.add(f.getButton());
		}
		
		// Tools
		JToolBar westToolbar = new JToolBar();
		westToolbar.setOrientation(SwingConstants.VERTICAL);
		westToolbar.setFloatable(false);
		frmBz.getContentPane().add(westToolbar, BorderLayout.WEST);

		// Tools button group
		toolButtons = new ButtonGroup();
		for( Tool tool : tools ){
			JToggleButton button = tool.getButton();
			westToolbar.add(button);
			toolButtons.add(button);
		}
		
		JScrollPane scrollPane = new JScrollPane(stitchPanel);
		frmBz.getContentPane().add(scrollPane, BorderLayout.CENTER);
		
		JMenuBar menuBar = new JMenuBar();
		frmBz.setJMenuBar(menuBar);
		
		// --- Menus ---
		
		// File
		fileMenu = new JMenu();
		menuBar.add(fileMenu);
		
		fileMenu.add(newFunction.getMenuItem());
		fileMenu.add(openFunction.getMenuItem());
		fileMenu.add(saveFunction.getMenuItem());
		fileMenu.add(saveAsFunction.getMenuItem());
		fileMenu.add(new JSeparator());
		fileMenu.add(printPreviewFunction.getMenuItem());
		fileMenu.add(printFunction.getMenuItem());
		fileMenu.add(pageSetupFunction.getMenuItem());
		fileMenu.add(new JSeparator());
		fileMenu.add(quitFunction.getMenuItem());
		
		// Edit
		editMenu = new JMenu();	
		editMenu.add(undoFunction.getMenuItem());
		editMenu.add(redoFunction.getMenuItem());
		menuBar.add(editMenu);
		
		// View
		viewMenu = new JMenu();
		languageMenu = new JMenu();
		
		// Language
		languageGroup = new ButtonGroup();
		JRadioButtonMenuItem englishMenu = languages.getRadioButton(Locale.ENGLISH);
		JRadioButtonMenuItem japaneseMenu = languages.getRadioButton(Locale.JAPANESE);
		JRadioButtonMenuItem italianMenu = languages.getRadioButton(Locale.ITALIAN);
		languageGroup.add(englishMenu);
		languageGroup.add(japaneseMenu);
		languageGroup.add(italianMenu);
		languageMenu.add(englishMenu);
		languageMenu.add(japaneseMenu);
		languageMenu.add(italianMenu);
		
		// Frame
		JCheckBoxMenuItem showFrame = new JCheckBoxMenuItem(getUIString("menu.view.showframe"));
		showFrame.setSelected(stitchPanel.isShowFrame());
		showFrame.setMnemonic(KeyEvent.VK_F);
		showFrame.addItemListener(new ItemListener() {
			
			@Override
			public void itemStateChanged(ItemEvent e) {
				stitchPanel.setShowFrame(e.getStateChange()==ItemEvent.SELECTED);
				stitchPanel.repaint();
			}
		});
		
		
		viewMenu.add(languageMenu);
		viewMenu.add(showFrame);
		menuBar.add(viewMenu);
		
		// Image
		imageMenu = new JMenu();
		menuBar.add(imageMenu);
		
		// Help
		helpMenu = new JMenu();
		menuBar.add(helpMenu);
		
		helpMenu.add(aboutFunction.getMenuItem());
		
		// Create a new stitch 
		setStitchPattern(stitchPanel.getStitchPattern(), null);
		
		// Set current language
		setLanguage(Locale.getDefault());
	}

	public String getToolMode() {
		if( toolButtons.getSelection() == null ) return "";
		return toolButtons.getSelection().getActionCommand();
	}

	public History getHistory() {
		return history;
	}
	
	public StitchPanel getStitchPanel() {
		return stitchPanel;
	}

	public Bead getPaletteBead() {
		return beadList.getSelectedValue();
	}
	
	public JList<BeadUsage> getUsedBeads() {
		return usedBeads;
	}

	public JFrame getFrame() {
		return frmBz;
	}

	/**
	 * @return the currentFile
	 */
	public File getCurrentFile() {
		return currentFile;
	}

	/**
	 * @param currentFile the currentFile to set
	 */
	public void setCurrentFile(File currentFile) {
		this.currentFile = currentFile;
	}

	/**
	 * @return the pageFormat
	 */
	public PageFormat getPageFormat() {
		return pageFormat;
	}

	/**
	 * @param pageFormat the pageFormat to set
	 */
	public void setPageFormat(PageFormat pageFormat) {
		this.pageFormat = pageFormat;
	}

	public void redraw() {			
		// Repaint window
		stitchPanel.repaint();
		// Repaint list
		usedBeads.repaint();
	}

	@SuppressWarnings("unchecked")
	public void fireUsedBeadsChange(){
		((LinkedMapWrapperModel<Integer, BeadUsage>) usedBeads.getModel()).fireChange();
	}
	
	@SuppressWarnings("unchecked")
	public void fireBeadsDBChange(){
		((LinkedMapWrapperModel<Integer, Bead>) beadList.getModel()).fireChange();
	}

	/**
	 * Make a brand new stitch pattern, and updates all the related GUI
	 */
	public void setStitchPattern(StitchPattern newStitchPattern, File file){
		currentFile = file;
		stitchPanel.setStitchPattern( newStitchPattern );
		
		// Window title
		resetWindowTitle();
		
		// Clear history
		history.clear();
		
		// Pattern type combo
		stitchPatternCombo.setSelectedItem(stitchPanel.getStitchPattern().getStitchType());
		
		// Size spinners
		widthSpinner.setModel(new SpinnerNumberModel(stitchPanel.getStitchPattern().getWidth(), 3, StitchPattern.MAX_SIZE, 1));
		heightSpinner.setModel(new SpinnerNumberModel(stitchPanel.getStitchPattern().getHeight(), 3, StitchPattern.MAX_SIZE, 1));
		
		// Use beads list
		usedBeads.setModel(new LinkedMapWrapperModel<Integer, BeadUsage>(stitchPanel.getStitchPattern().getUsedBeads()));
		
		// Update
		fireUsedBeadsChange();
		redraw();
	}

	/**
	 * Show confirmation dialog on whether to save of not to save
	 * @return
	 */
	public boolean askForSave() {
		// If no changes, no need to save
		if( !getHistory().hasChanges() ) return true;
		
		// Ask whether to save
		int res = JOptionPane.showConfirmDialog(
				frmBz, 
				getUIString("messages.confirm.save.message"), 
				getUIString("messages.confirm.save.title"),
				JOptionPane.YES_NO_CANCEL_OPTION);
		switch( res ){
		case JOptionPane.YES_OPTION:
			return saveAction.doSave();
			
		case JOptionPane.NO_OPTION:
			return true;
			
		case JOptionPane.CANCEL_OPTION:
			return false;
		}
		
		return false;
	}

	/**
	 * Register a modifying command to history
	 * @param command
	 */
	public void addCommand(Command command) {
		getHistory().addCommand(command);
		resetWindowTitle();
	}
	
	/**
	 * Undo last command
	 */
	public void undo(){
		getHistory().undo();
		resetWindowTitle();
	}
	
	/**
	 * Redo last undoed command
	 */
	public void redo(){
		getHistory().redo();
		resetWindowTitle();
	}

	/**
	 * Save operation success notify
	 * @param file
	 */
	public void onSave(File file) {
		currentFile = file;
		getHistory().onSave();
		resetWindowTitle();
	}

	/**
	 * Set appropriately the window title
	 */
	public void resetWindowTitle() {
		String title = PROGRAM_NAME;
		
		// Unsaved?
		if( getHistory().hasChanges() || getCurrentFile() == null ) title += "*";
		
		title += " - ";
		
		// File name
		if( getCurrentFile() != null ) title += getCurrentFile().getName();
		else title += getUIString("title.untitled");
		
		frmBz.setTitle(title);
	}
	
	/**
	 * Gets an internationalized string for the GUI bundle
	 * @param string
	 * @return
	 */
	public static String getUIString(String id) {
		return ResourceBundle.getBundle(GUI_RESOURCES, Locale.getDefault()).getString(id);
	}
	
	/**
	 * Sets a language and update all the GUI
	 * @param language
	 */
	public void setLanguage(Locale language) {
		Locale.setDefault(language);
		JOptionPane.setDefaultLocale(language);
		
		// Tabs
		eastPane.setTitleAt(0, getUIString("tabs.pattern"));
		eastPane.setTitleAt(1, getUIString("tabs.palette"));
		
		//Tools
		for( Tool t : tools ){
			t.updateText();
		}
		
		// Functions
		for( Function f : allFunctions ){
			f.updateText();
		}
		
		// Menus
		fileMenu.setText(getUIString("menu.file"));
		editMenu.setText(getUIString("menu.edit"));
		viewMenu.setText(getUIString("menu.view"));
		imageMenu.setText(getUIString("menu.image"));
		helpMenu.setText(getUIString("menu.help"));
		languageMenu.setText(getUIString("menu.language"));
		
		// Title
		resetWindowTitle();
		
		// Update language menu
		JRadioButtonMenuItem languageButton = languages.getRadioButton(language);
		if( languageButton == null ) {
			languageButton = languages.getRadioButton(Locale.ENGLISH);
		}
		
		languageGroup.setSelected(languageButton.getModel(), true);
	}
}
